package com.black.cat.jvm.rtda.heap;

import com.black.cat.jvm.classfile.ClassFile;
import com.black.cat.jvm.classpath.Classpath;
import com.black.cat.jvm.rtda.JObject;
import com.black.cat.jvm.rtda.heap.constantpool.AccessFlags;
import com.black.cat.jvm.rtda.heap.constantpool.RunTimeConstantPool;
import com.black.cat.jvm.rtda.heap.methodarea.JClass;
import com.black.cat.jvm.rtda.heap.methodarea.JField;
import com.black.cat.jvm.rtda.heap.methodarea.Slots;
import com.black.cat.jvm.rtda.heap.methodarea.StringPool;

import java.util.HashMap;
import java.util.Map;

/**
 * @Author blackcat
 * @create 2021/8/14 10:47
 * @version: 1.0
 * @description:类加载器
 */
public class ClassLoader {
    //Classpath指针
    private Classpath classpath;
    //已经加载的类数据
    private Map<String, JClass> classMap;

    public ClassLoader(Classpath classpath) {
        this.classpath = classpath;
        this.classMap = new HashMap<>();
    }

    //把类数据加载到方法区
    public JClass loadClass(String className) {
        JClass clazz = classMap.get(className);
        if (null != clazz) return clazz;
        //'['数组标识
        if (className.getBytes()[0] == '[') {
            return this.loadArrayClass(className);
        }
        try {
            return loadNonArrayClass(className);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    private JClass loadArrayClass(String className) {
        JClass clazz = new JClass(AccessFlags.ACC_PUBLIC,
                className,
                this,
                true,
                this.loadClass("java/lang/Object"),
                new JClass[]{this.loadClass("java/lang/Cloneable"), this.loadClass("java/io/Serializable")});
        this.classMap.put(className, clazz);
        return clazz;
    }

    private JClass loadNonArrayClass(String className) throws Exception {
        //加载  读取二进制文件 --> ClassFile --> class
        byte[] data = this.classpath.readClass(className);
        if (null == data) {
            throw new ClassNotFoundException(className);
        }
        JClass clazz = defineClass(data);
        //连接(验证 -->初始化 -->解析)
        link(clazz);
        return clazz;
    }

    private void link(JClass clazz) {
        verify(clazz);
        prepare(clazz);
    }

    private void prepare(JClass clazz) {
        calcInstanceFieldSlotIds(clazz);
        calcStaticFieldSlotIds(clazz);
        allocAndInitStaticVars(clazz);
    }

    private void allocAndInitStaticVars(JClass clazz) {
        clazz.staticVars = new Slots(clazz.staticSlotCount);
        for (JField field : clazz.JFields) {
            if (field.isStatic() && field.isFinal()) {
                initStaticFinalVar(clazz, field);
            }
        }
    }

    private void initStaticFinalVar(JClass clazz, JField field) {
        Slots staticVars = clazz.staticVars;
        RunTimeConstantPool constantPool = clazz.runTimeConstantPool;
        int cpIdx = field.constValueIndex();
        int slotId = field.slotId();

        if (cpIdx > 0) {
            switch (field.descriptor()) {
                case "Z":
                case "B":
                case "C":
                case "S":
                case "I":
                    java.lang.Object val = constantPool.getConstants(cpIdx);
                    staticVars.setInt(slotId, (Integer) val);
                    break;
                case "J":
                    staticVars.setLong(slotId, (Long) constantPool.getConstants(cpIdx));
                    break;
                case "F":
                    staticVars.setFloat(slotId, (Float) constantPool.getConstants(cpIdx));
                    break;
                case "D":
                    staticVars.setDouble(slotId, (Double) constantPool.getConstants(cpIdx));
                    break;
                case "Ljava/lang/String;":
                    String goStr = (String) constantPool.getConstants(cpIdx);
                    JObject jStr = StringPool.jString(clazz.loader, goStr);
                    staticVars.setRef(slotId, jStr);
                    break;
            }
        }

    }

    private void calcStaticFieldSlotIds(JClass clazz) {
        int slotId = 0;
        for (JField field : clazz.JFields) {
            if (field.isStatic()) {
                field.slotId = slotId;
                slotId++;
                if (field.isLongOrDouble()) {
                    slotId++;
                }
            }
        }
        clazz.staticSlotCount = slotId;
    }

    private void calcInstanceFieldSlotIds(JClass clazz) {
        int slotId = 0;
        if (clazz.superJClass != null) {
            slotId = clazz.superJClass.instanceSlotCount;
        }
        for (JField field : clazz.JFields) {
            if (!field.isStatic()) {
                field.slotId = slotId;
                slotId++;
                if (field.isLongOrDouble()) {
                    slotId++;
                }
            }
        }
        clazz.instanceSlotCount = slotId;
    }

    private void verify(JClass clazz) {
        // 校验字节码，尚未实现
    }

    private JClass defineClass(byte[] data) throws Exception {
        JClass clazz = parseClass(data);
        clazz.loader = this;
        resolveSuperClass(clazz);
        resolveInterfaces(clazz);
        this.classMap.put(clazz.name, clazz);
        return clazz;
    }

    private void resolveInterfaces(JClass clazz) throws Exception {
        int interfaceCount = clazz.interfaceNames.length;
        if (interfaceCount > 0) {
            clazz.interfaces = new JClass[interfaceCount];
            for (int i = 0; i < interfaceCount; i++) {
                clazz.interfaces[i] = clazz.loader.loadClass(clazz.interfaceNames[i]);
            }
        }
    }

    private void resolveSuperClass(JClass clazz) throws Exception {
        if (!clazz.name.equals("java/lang/Object")) {
            clazz.superJClass = clazz.loader.loadClass(clazz.superClassName);
        }
    }

    private JClass parseClass(byte[] data) {
        ClassFile classFile = new ClassFile(data);
        return new JClass(classFile);
    }
}
